////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//http://www.adobe.com/cfusion/exchange/index.cfm?event=extensionDetail&extid=1049969
//
////////////////////////////////////////////////////////////////////////////////

package br.com.aspen.components
{

	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TextEvent;
	import flash.ui.Keyboard;
	import mx.controls.TextInput;
	import mx.core.Application;
	import mx.core.EdgeMetrics;
	import mx.core.IFlexDisplayObject;
	import mx.core.IInvalidating;
	import mx.core.UIComponent;
	import mx.core.UITextField;
	import mx.core.mx_internal;
	import mx.managers.FocusManager;
	import mx.skins.Border;
	import mx.skins.ProgrammaticSkin;
	import mx.styles.ISimpleStyleClient;
	import mx.skins.RectangularBorder;
	import flash.text.TextFormat;
	import flash.text.TextField;
	import mx.controls.TextArea;
	import flash.utils.getTimer;

//import com.adobe.flex.extras.skins.MTISkin;

	use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

	/**
	 *  Dispatched when the last character is entered in the
	 *  Masked Text Input control through user input.
	 *
	 *  @eventType flash.events.Event
	 *  @tiptext inputMaskEnd event
	 */
	[Event(name="inputMaskEnd", type="flash.events.Event")]

//--------------------------------------
//  Styles
//--------------------------------------

	/**
	 * The color of the background cell.
	 *  @default 0xFF0000 if required property is set, 0x008CEA otherwise
	 */
	[Style(name="cellColor", type="uint", format="Color",inherit="no")]

	/**
	 * The color of the text when  required property is set,
	 *  and the field is left incomplete.
	 *  @default 0xFF0000
	 */
	[Style(name="errorTextColor", type="uint", format="Color",inherit="no")]

//--------------------------------------
//  Skins
//--------------------------------------

	/**
	 *  Name of the class to use as the skin for the background.
	 *
	 *  @default "com.adobe.flex.extras.controls.skins.MTISkin"
	 */
	[Style(name="MTISkin", type="Class", inherit="no")]


	/**
	 * 	The Masked Text Input component is a single-line,
	 * 	text input field. This component adds support for
	 * 	the validation of input against a specified mask expression.
	 * 	For using the control, input mask should be provided
	 * 	by setting the <code>inputMask</code> property of the
	 * 	control. This control shows meaningful behavior only
	 * 	when <code>inputMask</code> is specified in a correct manner.
	 *
	 * 	<p>If the mask is not specified correctly or it is
	 * 	not provided, then the control will work as a
	 * 	standard Text Input control.
	 *
	 * 	@mxml
	 *
	 * 	<p>The <code>&lt;mx:MaskedTextInput&gt;</code> tag inherits the attributes
	 * 	of its superclass and adds the following attributes:</p>
	 *
	 *  <pre>
	 *  &lt;fc:MaskedTextInput
	 *    <b>Properties</b>
	 *    inputMask=""
	 * 	  text=""
	 * 	  fullText=""
	 * 	  defaultCharacter="_"
	 * 	  required=false
	 * 	  autoAdvance=false
	 * 	  &nbsp;
	 *    <b>Styles</b>
	 *    cellColor="0x008CEA"
	 * 	  errorTextColor="0xFF0000"
	 * 	  &nbsp;
	 *    <b>Events</b>
	 * 	  inputMaskEnd="<i>No default</i>"
	 *  /&gt;
	 *  </pre>
	 *
	 *  @includeExample ../../../../../../docs/com/adobe/flex/extras/controls/example/MaskedTextInputExample/MaskedTextInputExample.mxml
	 *
	 *  @see mx.controls.TextInput
	 *
	 *  @tiptext TextInput is a single-line, editable text field.
	 *
	 */
	public class MaskedTextInput extends TextInput
	{	
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------

		/**
		 *  Constructor.
		 */
		public function MaskedTextInput()
		{
			super();

			// Adding Listeners for various events
			addEventListener(TextEvent.TEXT_INPUT,interceptChar,true,0);
			addEventListener(MouseEvent.CLICK,reposition,true);
			addEventListener(MouseEvent.MOUSE_DOWN,reposition,true);
			addEventListener(KeyboardEvent.KEY_DOWN,interceptKey,true);
			addEventListener(FocusEvent.FOCUS_IN,interceptFocus,false);
			addEventListener(FocusEvent.FOCUS_OUT,interceptFocus);
			addEventListener(Event.CHANGE,menuHandler);

			// commented to retain the default Tab and Shift-Tab behavior
			// uncomment if you want the focus to be shifted to the
			// next/previous sub-field in case of a Tab/Shift-Tab respectively.
			//addEventListener(FocusEvent.KEY_FOCUS_CHANGE,tabHandler);


			this.doubleClickEnabled = true;
			addEventListener(MouseEvent.DOUBLE_CLICK,handleDoubleClick,true);

		}

		//--------------------------------------------------------------------------
		//
		//  Variables
		//
		//--------------------------------------------------------------------------

		/**
		 *  @private
		 * The UITextField control for displaying embeded hints.
		 */
		private var embedTextField:UITextField;

		/**
		 *  @private
		 *  Storage for Escape character.
		 */
		private const ESCAPE:String = "/";

		/**
		 *  Internal array to store the data in the Text Field.
		 */
		mx_internal var _working:Array = [];

		/**
		 *  @private
		 *  Storage for current cursor position.
		 */
		private var _position:Number = 0;

		/**
		 *  @private
		 *  Flag set when focus is set by using mouse click.
		 */
		private var focusByClick:Boolean = false;

		/**
		 *  @private
		 *  Storage for the time when double click event happened.
		 */
		private var lastTime:Number = 0;

		/**
		 *  @private
		 *  Flag set when _working is updated.
		 */
		private var workingUpdated:Boolean = false;

		/**
		 *  @private
		 *  Flag set when inputMask is updated.
		 */
		private var maskUpdated:Boolean = false;

		/**
		 *  @private
		 *  Flag set when text is updated.
		 */
		private var textUpdated:Boolean = false;

		/**
		 *  @private
		 *  Flag set when font-family/font-size is updated.
		 */
		private var fontUpdated:Boolean = false;

		/**
		 *  @private
		 *  Flag set when either the mask is entered wrongly or is not specified.
		 *  The control will work as a standard Text Input if defaultBehaviour is set
		 */
		private var defaultBehaviour:Boolean = true;

		/**
		 *  @private
		 *  Storage for the length of the Text Field.
		 */
		private var actualLength:Number=0;

		/**
		 *  @private
		 *  The String to be shown as the embeded hint.
		 */
		private var embedStr:String = "";

		/**
		 *  @private
		 *  Specifies that the field is a date only field.
		 */
		private var dateField:Boolean = false;

		/**
		 *  @private
		 *  Skin names.
		 *  Allows subclasses to re-define the skin property names.
		 */
		mx_internal var backgroundSkinName:String = "MTISkin";

		/**
		 *  @private
		 *  Storage for the autoAdvance property.
		 */
		private var _autoAdvance:Boolean = false;

		[Bindable("change")]
		[Inspectable(category="General", defaultValue="false")]
		/**
		 *  If set to true, focus will automatically move to the next field
		 *  in the tab order.
		 *
		 *  @default false
		 */
		public function set autoAdvance( value:Boolean ) : void
		{
			_autoAdvance = value;
		}

		public function get autoAdvance() : Boolean
		{
			return _autoAdvance;
		}

		/**
		 *  @private
		 *  Storage for the required property.
		 */
		private var _required:Boolean = false;

		[Bindable("change")]
		[Inspectable(category="General", defaultValue="false")]
		/**
		 *  If set and if the field is left incomplete,
		 *  the color of the characters in the field will be changed
		 *  to errorTextColor or to red if errorTextColor is not specified,
		 *
		 *  @default false
		 */
		public function set required( value:Boolean ) : void
		{
			_required = value;
			// when required is changed dynamically, 
			// then the change should be reflected.
			invalidateDisplayList();
		}

		public function get required() : Boolean
		{
			return _required;
		}

		/**
		 *  @private
		 *  Storage for the defaultCharacter property.
		 */
		private var _defaultCharacter:String = "_";

		[Bindable("change")]
		[Inspectable(category="General", defaultValue="_")]
		/**
		 *  The character used in place of an empty space
		 *  when fullText is returned.
		 *
		 *  @default "_"
		 */
		public function set defaultCharacter( str:String ) : void
		{
			_defaultCharacter = str.charAt(0);
		}
		public function get defaultCharacter() : String
		{
			return _defaultCharacter;
		}

		/**
		 *  @private
		 *  Storage for the input mask.
		 */
		private var _inputMask:String="";

		[Bindable("change")]
		[Inspectable(category="General", defaultValue="")]
		/**
		 *  The input mask used for validating the input.
		 *
		 */
		public function get inputMask() : String
		{
			return _inputMask;
		}
		public function set inputMask(s:String) : void
		{
			maskMap = [];
			_inputMask = s;
			dateField = false;
			switch(_inputMask)
			{
				case "MM/DD/YYYY" : _inputMask = "##//##//####";
					embedStr = "MM DD YYYY";
					dateField = true;
					break;
				case "DD/MM/YYYY" : _inputMask = "##//##//####";
					embedStr = "DD MM YYYY";
					dateField = true;
					break;
				case "YYYY/MM/DD" : _inputMask = "####//##//##";
					embedStr = "YYYY MM DD";
					dateField = true;
					break;
				case "YYYY/DD/MM" : _inputMask = "####//##//##";
					embedStr = "YYYY DD MM";
					dateField = true;
					break;
				default: break;
			}

			defaultBehaviour = false;
			checkMask();
			actualLength = calculateMaxChars();
			if(!defaultBehaviour)
				maxChars = actualLength;
			maskUpdated = true;
			invalidateDisplayList();
		}

		/**
		 *  @private
		 *  Storage for the text property.
		 */
		private var _text:String = "";

		[Bindable("change")]
		[Bindable("valueCommit")]
		[Inspectable(category="General", defaultValue="")]

		/**
		 *  Returns only the text entered by the user.
		 */
		override public function get text():String
		{
			if(defaultBehaviour)
				return super.text;

			var result:String = "";
			for(var i:Number=0; i < _working.length; i++) {
				var c:String = _working[i];
				if(!maskMap[i][1])
				{
					continue;
				}
				else if(c == " ")
					c = "";	
				result += c;
			}
			return result;
		}

		override public function set text(value:String):void
		{
			if(defaultBehaviour)
			{
				super.text = value;
				//invalidateDisplayList();
				return;
			}
			_text = value;
			textUpdated = true;
			invalidateDisplayList();
		}

		[Bindable("change")]
		[Bindable("valueCommit")]
		[Inspectable(category="General", defaultValue="")]
		/**
		 *  Returns the whole text displayed in the Text Field.
		 */
		public function get fullText() : String
		{
			if(defaultBehaviour)
				return super.text;

			var result:String = "";
			for(var i:Number=0; i < _working.length; i++) {
				var c:String = _working[i];
				if( c == " " ) c = _defaultCharacter;
				result += c;
			}
			return result;
		}

		/**
		 *  Internal array to store each character in the mask
		 *  and whether it is to be entered by the user or displayed as it is.
		 */
		mx_internal var maskMap:Array;

		/**
		 *  @private
		 *  Calculate the number of characters which are displayed
		 *  (i.e., number of character in the inputMask excluding
		 *  the ESCAPE character) and can be entered in the text field.
		 *  Also populate the array maskMap which specifies that
		 *  a character at a particular position is to be
		 *  entered by the user or displayed as it is.
		 */
		private function calculateMaxChars():Number
		{
			maskMap = new Array();
			var count:Number = 0;
			for(var i:int=0,k:int=0;i<_inputMask.length;i++,k++)
			{
				if((_inputMask.charAt(i) == ESCAPE && _inputMask.charAt(i+1) == ESCAPE)
					|| (_inputMask.charAt(i) == ESCAPE && isMask(_inputMask.charAt(i+1))))
				{
					maskMap[k] = [_inputMask.charAt(i+1), false];
					i++;		
				}
				else if(isMask(_inputMask.charAt(i)))
				{
					maskMap[k] = [_inputMask.charAt(i), true];
				}
				else
					maskMap[k] = [_inputMask.charAt(i), false];
				count++;
			}
			return count;
		}

		/**
		 *  @private
		 *  Check for the validity of the mask specified.
		 *  If the mask is not valid then set the flag defaultBehaviour to true.
		 *  If flag defaultBehaviour is set then the Masked Text Input Control will
		 *  function as a standard Text Input control.
		 */
		private function checkMask():void
		{
			if(!_inputMask || _inputMask == "")
			{
				defaultBehaviour = true;
				return ;
			}
			for(var i:int=0;i<_inputMask.length;i++)
			{	
				if((_inputMask.charAt(i) == ESCAPE && _inputMask.charAt(i+1) == ESCAPE)
					|| (_inputMask.charAt(i) == ESCAPE && isMask(_inputMask.charAt(i+1))))
				{
					i++;
					continue;
				}
				else if(_inputMask.charAt(i) == ESCAPE 
					&& (_inputMask.charAt(i+1) != ESCAPE || !isMask(_inputMask.charAt(i+1))))
				{
					defaultBehaviour = true;
					break;
				}
				else if(isMask(_inputMask.charAt(i)))
					continue;
			}
		}

		/**
		 *  @private
		 *  Create child objects for displaying embeded hints.
		 */
		override protected function createChildren():void
		{
			super.createChildren();

			if(dateField)
			{
				embedTextField = new UITextField();
				embedTextField.text = embedStr;
					//addChildAt(embedTextField,getChildIndex(textField));
			}
		}

		//--------------------------------------------------------------------------
		//
		//  Event Handlers
		//
		//--------------------------------------------------------------------------

		/**
		 *  @private
		 *  Handles cut and do nothing.
		 */
		private function menuHandler(event:Event):void
		{
			// In case of a standard text field, perform the default cut action
			if(defaultBehaviour)
			{
				return ;
			}
			var str:String = super.text;
			super.text = _working.join("");
			event.preventDefault();
			return;
		}

		/**
		 *  @private
		 *  Handles TAB and SHIFT+TAB event and repositions the insertion point.
		 *  The insertion point(cursor) is moved to the position of the
		 *  next/previous subfield respectively.
		 */
		//commented to retain the default Tab and Shift-Tab behavior
		//uncomment if you want the focus to be shifted to the
		// next/previous sub-field in case of a Tab/Shift-Tab respectively.
		/* private function tabHandler(event:FocusEvent):void
		   {
		   // Move the insertion point to the previous group of mask characters
		   if(event.shiftKey && event.keyCode == Keyboard.TAB)
		   {
		   var foundMask:Boolean = false;
		   if(!(_position <=0))
		   {
		   for(var i:int=_position; i >= 0; i--)
		   {
		   if(foundMask && (!maskMap[i][1] || i==0))
		   {
		   if(!maskMap[i][1])
		   {
		   _position = i + 1;
		   setSelection(_position,_position);
		   event.preventDefault();
		   break;
		   }
		   if(i==0)
		   {
		   _position = 0;
		   setSelection(_position,_position);
		   event.preventDefault();
		   break;
		   }
		   }

		   if(!foundMask && (i>0 && i<actualLength) && !maskMap[i][1] && maskMap[i-1][1])
		   {
		   foundMask = true;
		   }

		   }
		   changeSelection(_position);
		   }
		   }
		   // Move the insertion point to the next group of mask characters
		   else if(event.keyCode == Keyboard.TAB)
		   {
		   if(_position != actualLength-1)
		   {
		   for(i=_position; i < actualLength; i++)
		   {
		   if(!maskMap[i][1] && maskMap[i+1][1])
		   {
		   _position = i + 1;
		   setSelection(_position,_position);
		   changeSelection(_position);
		   event.preventDefault();
		   break;
		   }
		   }
		   }
		   }
		 } */

		/**
		 *  @private
		 *  Handles MOUSE_CLICK event and repositions the insertion point.
		 *  The insertion point(cursor) is moved to the position of the
		 *  mouse click if all the characters in the subfield(to the left
		 *  of the click point) are filled.
		 */
		private function reposition( event:flash.events.MouseEvent ) : void
		{
			if(defaultBehaviour)
			{
				return ;
			}

			// Adding MOUSE_UP event listener
			if(event.type == MouseEvent.MOUSE_DOWN)
				event.preventDefault();

			// Handles triple click
			var now:Number = getTimer();
			if(lastTime != 0 && (now - lastTime) < 300)
			{
				lastTime = 0;
				setSelection(0,actualLength);
				event.preventDefault();
				return ;
			}

			// Changed so that cursor can not be positioned to a subfield
			// if any of the previous subfield is partially filled.
			//if(this.selectionBeginIndex <= actualLength 
			//	&& _working[this.selectionBeginIndex-1] == " ")
			if(this.selectionBeginIndex <= actualLength 
				&& isWorkingIncomplete(this.selectionEndIndex))
			{
				//if(maskMap[this.selectionBeginIndex-1][1])
				setSelection(_position,_position);
			}
			else
			{
				_position = this.selectionBeginIndex;
				if(focusByClick)
				{
					setSelection(_position,_position);
					focusByClick = false;
				}
			}
			event.preventDefault();
		}

		/**
		 *  @private
		 *  Handles MOUSE_DOUBLECLICK event and selects the sub field.
		 */
		private function handleDoubleClick(event:MouseEvent):void
		{
			if(defaultBehaviour)
				return ;

			textField.selectable = false;
			lastTime = getTimer();

			var startPos:int = textField.getCharIndexAtPoint(event.localX,event.localY) != -1 ?
				textField.getCharIndexAtPoint(event.localX,event.localY) : 
				(event.localX < 10 ? 0 : (actualLength -1) );

			while(startPos>0 && startPos<actualLength && maskMap[startPos-1][1])
				startPos--;

			if(!isWorkingIncomplete(startPos))
				changeSelection(startPos);

			event.stopImmediatePropagation();
			event.preventDefault();

			textField.selectable = true;

			return ;
		}

		/**
		 *  @private
		 *  Handles key press event for special keys like Delete, Backspace, etc.
		 */
		private function interceptKey( event:flash.events.KeyboardEvent ) : void
		{
			if(defaultBehaviour)
			{
				super.keyDownHandler(event);
				return ;
			}

			// Delete one character before the insertion point 
			// and moves the insertion point to one position back.
			if( event.keyCode == Keyboard.BACKSPACE ) {
				_position = selectionBeginIndex;
				handleDeletions(true);
			}
			// Delete one character at the insertion point 
			else if( event.keyCode == Keyboard.DELETE ) {
				handleDeletions();
			}
			// Moves the insertion point to the previous viable input position
			else if( event.keyCode == Keyboard.LEFT ) {
				if(_position >0 && _working[_position-1] == " ")
				{
					setSelection(_position,_position);
				}
				else
				{
					_position = this.selectionBeginIndex;
					retreatPosition();
				}
				event.preventDefault();
			}
			// Moves the insertion point to the next viable input position
			else if( event.keyCode == Keyboard.RIGHT ) {
				if(_position == actualLength -1)
				{
					++_position;
					setSelection(_position,_position);
				}
				else if(_position < actualLength && _working[_position] == " ")
				{
					setSelection(_position,_position);
				}
				else
					advancePosition(); 

				event.preventDefault();
			}
			// Moves the insertion point to the last viable input position
			else if( event.keyCode == Keyboard.END ) {
				var b:Boolean = false;
				for(var i:int = _position; i < actualLength; i++)
				{
					if(_working[i] == " ")
					{
						_position = i;
						setSelection(i,i);
						b = true;
						break;
					}
				}
				if(!b)
					_position = _working.length;
				event.preventDefault();
			}
			// Moves the insertion point to the first viable input position
			else if( event.keyCode == Keyboard.HOME ) {
				_position = -1;
				advancePosition(true);
			}
			workingUpdated = true;
			invalidateDisplayList();
		}

		/**
		 *  @private
		 *  Handle TEXT_INPUT events by matching the character with
		 *  the mask and either blocking or allowing the character.
		 */
		private function interceptChar( event:TextEvent ) : void
		{
			// If the mask is incorrect or not spceified then treat the control
			// as a standard TextInput control
			if(defaultBehaviour)
			{
				return ;
			}

			// Get the typed characters
			var input:String = event.text;

			if( _position >= actualLength ) {
				event.preventDefault();

				// If autoAdvance flag is set, then set the focus to the
				// next control according to the tab index.
				if(_autoAdvance && !isWorkingIncomplete())
				{
					var obj:UIComponent = UIComponent(focusManager.getNextFocusManagerComponent());
					if((obj is MaskedTextInput) || (obj is TextInput) || (obj is TextArea))
						obj.setFocus();
				}

				// Dispatch the inputMaskEnd Event
				dispatchEvent(new Event("inputMaskEnd"));
				return;
			}

			handleInput(input,event);
		}

		/**
		 *  @private
		 *  Consumes the FOCUS_IN and FOCUS_OUT event and repositions the insertion
		 *  point and perform additional checks based on the required property.
		 */
		private function interceptFocus( event:FocusEvent ) : void
		{
			if(defaultBehaviour)
			{
				return ;
			}

			if(event.type == FocusEvent.FOCUS_IN)
			{
				focusByClick = true;
				// If tab is used to move within the fields of the control,
				// then position the insertion point to the begining of the
				// next group of mask characters
				_position = -1;

				// advance the insertion point to the first viable input field.
				advancePosition();
				// selects the current subfield
				changeSelection(0);
			}
			else if(event.type == FocusEvent.FOCUS_OUT)
			{
				textField.setColor(0x000000);
				if(_required && isWorkingIncomplete())
				{
					var errorTextColor:Number = getStyle("errorTextColor");
					if(errorTextColor)
						textField.setColor(errorTextColor);
					else
						textField.setColor(0xFF0000);
				}
			}

		}

		/**
		 *  @private
		 *  Handles Deletion of characters caused by pressing
		 *  backspace/Delete key.
		 */
		private function handleDeletions(backSpace:Boolean = false):void
		{
			var i:int = 0;
			var s:String = "";
			if(this.selectionBeginIndex == this.selectionEndIndex || this.selectionBeginIndex == this.selectionEndIndex+1)
			{
				var startIndex:int = -1;
				var endIndex:int = actualLength;
				if(backSpace)
				{
					startIndex = 0;
					endIndex = actualLength + 1;
				}

				if(_position>startIndex && _position < endIndex)
				{
					if(backSpace)
						retreatPosition();

					i = _position;
					if(!maskMap[_position][1])
						_working[_position] = maskMap[_position][0];
					else
					{
						// Commented so that characters dont shift to left when deleting
						/* while((i+1) < actualLength && _working[i+1] != " " && maskMap[i+1][1])
						   {
						   _working[i] = _working[i+1];
						   i++;
						 } */
						_working[i] = " ";

						// Added so that delete can work fine.
						if(!backSpace && _position<actualLength-1 && _working[_position+1] != " ")
						{
							advancePosition();
								//setSelection(_position,_position);
						}

						if(dateField)
						{
							s = embedTextField.text == null ? "":embedTextField.text;
							if(s.length > 0)
							{
								s = s.substring(0,_position) + " " + s.substring(_position+1, s.length);
								s = s.substring(0,i) + embedStr.charAt(i) + s.substring(i+1, s.length);
								embedTextField.text = s;
							}
						}
					}
				}
			}
			else
			{
				_position = this.selectionBeginIndex;
				i = _position - 1;
				for(var j:int=this.selectionBeginIndex;j<this.selectionEndIndex;j++)
				{
					// Commented so that characters dont shift to left when deleting
					//i = _position;
					//if(!maskMap[_position][1])
					//	_working[_position] = maskMap[_position][0];
					i++;

					if(!maskMap[i][1])
						_working[i] = maskMap[i][0];
					else
					{
						// Commented so that characters dont shift to left when deleting
						/* while((i+1) < actualLength && _working[i+1] != " " && maskMap[i+1][1])
						   {
						   _working[i] = _working[i+1];
						   i++;
						 } */
						_working[i] = " ";

						if(dateField)
						{
							s = embedTextField.text == null ? "":embedTextField.text;
							if(s.length > 0)
							{
								// Commented so that characters dont shift to left when deleting
								//s = s.substring(0,_position) + " " + s.substring(_position+1, s.length);
								//s = s.substring(0,i) + embedStr.charAt(i) + s.substring(i+1, s.length);
								//embedTextField.text = s;

								s = s.substring(0,i) + " " + s.substring(i+1, s.length);
								s = s.substring(0,i) + embedStr.charAt(i) + s.substring(i+1, s.length);
								embedTextField.text = s;
							}
						}
					}
				}
			}
		}

		/**
		 *  @private
		 *  Change the selected text to select the text in the current subfield.
		 */

		private function changeSelection(pos:Number):void
		{
			var startPos:int = pos;

			while(!maskMap[startPos][1])
				startPos++;

			var endPos:int = startPos;

			while(endPos<actualLength && maskMap[endPos][1] && _working[endPos] !=" ")
				endPos++;
			setSelection(startPos,endPos);
		}

		/**
		 *  @private
		 *  Returns true if the text field is left incomplete.
		 */
		private function isWorkingIncomplete(len:Number = -1):Boolean
		{
			if(len == -1)
				len = actualLength;

			for(var i:int=0;i<len;i++)
			{
				if(_working[i] == " " && maskMap[i][1])
				{
					return true;
				}
			}
			return false;
		}

		/**
		 *  @private
		 *  Handle the text entered by the user or specified in the
		 *  text property.
		 */
		private function handleInput(input:String,event:Event = null):void
		{
			for(var i:int = 0;i<input.length;i++)
			{

				var c:String = input.charAt(i);
				var m:String;
				if(_position >= actualLength)
					return;

				var pos:Number = (_position < 0) ? 0:((_position >= actualLength)?actualLength-1:_position);
				if(pos < actualLength && maskMap[pos][0] != null)
					m = maskMap[pos][0];
				else
				{
					if(event != null)
						event.preventDefault();
					return;
				}

				// Flag set to false if the character is not accepted
				var bAdvance:Boolean = true;
				// Check the character entered with the mask character at that position
				switch(m)
				{
					case "#":
						// if the character is a digit, accept it
						if( isDigit(c) ) {
							allowChar(c);
						} else {
							//event.preventDefault();
							bAdvance = false;
						}
						break;
					case "A":
						// if the character is an alphabet, 
						// convert it to upper case and accept it.
						if( isLetter(c) ) {
							allowChar(c.toUpperCase());
						} else {
							//event.preventDefault();
							bAdvance = false;
						}
						break;
					case "a":
						// if the character is an alphabet, 
						// convert it to lower case and accept it.
						if( isLetter(c) ) {
							allowChar(c.toLowerCase());
						} else {
							//event.preventDefault();
							bAdvance = false;
						}
						break;
					case "B":
						// if the character is an alphabet, accept it.
						if( isLetter(c) ) {
							allowChar(c);
						} else {
							//event.preventDefault();
							bAdvance = false;
						}
						break;
					case "x":
						// if the character is not a digit, accept it.
						if( isDigit(c) ) {
							//event.preventDefault();
							bAdvance = false;
						} else {
							allowChar(c);
						}
						break;
					case "*":
						// if the character is a digit or an alphabet, accept it.
						if( isDigit(c) || isLetter(c) ) {
							allowChar(c);
						} else {
							//event.preventDefault();
							bAdvance = false;
						}
						break;	
					default:
						break;
				}		

				if( bAdvance ) {
					// If embeded hints are displayed then update the
					// UITextField corresponding to the embeded hints
					if(dateField)
					{
						var s:String = embedTextField.text == null ? "":embedTextField.text;
						if(s.length > 0)
						{
							s = s.substring(0,_position) + " " + s.substring(_position+1, s.length);
							embedTextField.text = s;
						}
					}

					advancePosition();
				}
				else
				{
					if(event != null)
						event.preventDefault();
				}

				workingUpdated = true;

				invalidateDisplayList();

			}
		}

		/**
		 *  @private
		 *  Moves the insertion point forward (if possible) to the next viable
		 *  input position.
		 *  byArrow denotes that advancePosition is called when
		 *  the user has pressed Arrow key or not.
		 */
		private function advancePosition(byArrow:Boolean=false) : void{
			var p:Number = _position;

			var posChanged:Boolean = false;

			while((++p) < actualLength)	{
				posChanged = true;
				if(p >= actualLength-1)	{
					p = actualLength - 1;
					break;
				}
				if(maskMap[p][1])
					break;
			}

			if(posChanged || p == actualLength)
				_position = p;

			// byArrow denotes that advancePosition is called when
			// the user has pressed Arrow key or not
			if( p >= actualLength && !byArrow ) {
				if(_autoAdvance && !isWorkingIncomplete()){
					// Going to the next field
					var obj:UIComponent = UIComponent(focusManager.getNextFocusManagerComponent());
					if((obj is MaskedTextInput) || (obj is TextInput) || (obj is TextArea))
						obj.setFocus();

				}
				dispatchEvent(new Event("inputMaskEnd"));
			}
			setSelection(_position,_position);
		}

		/**
		 *  @private
		 *  Moves the insertion point backward (if possible) to the previous
		 *  viable input position.
		 */
		private function retreatPosition() : void{	
			var p:Number = _position;
			var posChanged:Boolean = false;

			while((--p) >= 0 ){
				posChanged = true;
				if(p <= 0 || maskMap[p][1])
					break;
			}
			if(posChanged)
				_position = p;

			setSelection(_position,_position);
		}

		/**
		 *  @private
		 *  Returns true if the given character is a masking character.
		 */
		private function isMask( c:String ) : Boolean{
			return (c == "#" ||  c == "A" || c == "a" || c == "B" || c == "X" || c == "*");
		}

		/**
		 *  @private
		 *  Returns true if the given character is a digit.
		 */
		private function isDigit( c:String ) : Boolean{
			return ((c >= "0" && c <= "9"));
		}

		/**
		 *  @private
		 *  Returns true if the given character is an Alphabet.
		 */
		private function isLetter(c:String):Boolean{
			return (((c >= "a") && (c <= "z")) || ((c >= "A") && (c <= "Z")));
		}

		/**
		 *  @private
		 *  Inserts the character into the working array.
		 */
		private function allowChar( c:String ) : void{
			// Commented so that the characeters dont shift to their left when deleting
			/* var insertionPossible:Boolean = false;
			   var i:int = _position;
			   while((i+1) <= actualLength && maskMap[i][1])
			   {
			   if(_working[i] == " " && !(maskMap[i][0] == " "))
			   {
			   insertionPossible = true;
			   break;
			   }
			   i++;
			   }
			   while(insertionPossible && (i) > _position && maskMap[i][1])
			   {
			   _working[i] = _working[i-1];
			   i--;
			 } */
			_working[_position] = c;
		}

		/**
		 *  @private
		 *  Modifies the display according to how flags are set: if
		 *  text has been updated, fold the text according to the mask. If
		 *  the mask has been updated, modify the display.
		 */
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			if(defaultBehaviour){
				defaultBehaviour = true;
				super.updateDisplayList(unscaledWidth,unscaledHeight);
				return ;
			}

			super.updateDisplayList( unscaledWidth, unscaledHeight );

			if( maskUpdated ) {
				maskUpdated = false;

				_working = [];
				for(var i:int=0; i < actualLength; i++) {
					var c:String = " ";
					if(!maskMap[i][1])
						c = maskMap[i][0];
					_working.push(c);

				}

				workingUpdated = true;
			}

			if( textUpdated ) {
				textUpdated = false;
				_working = [];
				for(var j:int=0; j < actualLength; j++) {
					var ch:String = " ";
					if(!maskMap[j][1])
						ch = maskMap[j][0];
					_working.push(ch);

				}
				_position = 0;
				handleInput(_text);
				workingUpdated = true;
			}

			if( workingUpdated ) {
				super.text = _working.join("");
				workingUpdated = false;
			}

			if(fontUpdated)	{
				fontUpdated = false;

				// Set the font size to 72 if user has specified the
				// font size to be greater than 72
				if(getStyle("fontSize") > 72){
					setStyle("fontSize",72);
				}

				// Check if Monospaced font is used or not
				// Set the font to Courier if font used is other then a monospaced font
				if(measureText("W").width != measureText("I").width){
					setStyle("fontFamily","Courier");
				}

				// Set the width of the control
				width = measureText("W").width *  actualLength + 2*getStyle("borderThickness") + 5;
			}	
			if(dateField){
				// when inputMask is changed dynamically, 
				// then in case of a date mask,
				// create the embeded text field for showing
				// embeded hints if its not already created.
				// If it is created, then just change the embeded hint.
				if(!embedTextField)	{
					embedTextField = new UITextField();
						//addChildAt(embedTextField,getChildIndex(textField));
				}
				embedTextField.text = embedStr;

				embedTextField.alpha = 1;

				var txtFormat:TextFormat = new TextFormat();
				txtFormat.color = 0xFFFFFF;
				embedTextField.setTextFormat(txtFormat);
				embedTextField.x = textField.x;
				embedTextField.y = textField.y;
				embedTextField.setActualSize(width,height);
			}
			else{
				if(embedTextField){
					embedTextField.text = "";
					embedTextField = null;
				}
			}

			// create or updates the control's skin
			createSkin();

			textField.width += 50;
		}

		/**
		 *  @private
		 */
		override protected function measure():void{
			super.measure();
			var bm:EdgeMetrics = border && border is RectangularBorder ?
				RectangularBorder(border).borderMetrics :
				EdgeMetrics.EMPTY;

			measuredMinHeight = measureText("Wj").height+4+bm.top+bm.bottom;
			measuredHeight = measuredMinHeight;
		}

		/**
		 *  @private
		 */
		override public function styleChanged(styleProp:String):void{
			super.styleChanged(styleProp);

			// using date mask with validators changes the text color to black,
			// in case of a validation failure.
			// setting the color of the embeded text field to white.
			if(styleProp == "themeColor"){
				if(embedTextField)
					embedTextField.setColor(0xFFFFFF);
			}

			if(styleProp == "fontFamily" || styleProp == "fontSize"){
				fontUpdated = true;
				measuredMinHeight = measureText("Wj").height+4;
				measuredHeight = measuredMinHeight;
				invalidateDisplayList();

			}
		}

		/**
		 *  @private
		 */
		override public function getStyle(styleProp:String):*{
			if(styleProp == "cellColor")
				if(!super.getStyle("cellColor"))
					if(required)
						return 0xFF0000;
					else
						return 0x008CEA;

			if(styleProp == "errorTextColor")
				if(!super.getStyle("errorTextColor"))
					return 0xFF0000;

			return super.getStyle(styleProp);
		}

		/**
		 *  @private
		 */
		private function createSkin():void{
			var skinName:String = backgroundSkinName;

			// Has this skin already been created?
			var newSkin:IFlexDisplayObject =
				IFlexDisplayObject(getChildByName(skinName));

			// If not, create it.
			if (!newSkin){
				var newSkinClass:Class = Class(getStyle(skinName));
				if (!newSkinClass){
					//newSkinClass = MTISkin;
				}
				if(newSkinClass){
					newSkin = IFlexDisplayObject(new newSkinClass());

					// Set its name so that we can find it in the future
					// using getChildByName().
					newSkin.name = skinName;

					// Make the getStyle() calls in MTISkin find the styles
					// for this control.
					var styleableSkin:ISimpleStyleClient = newSkin as ISimpleStyleClient;
					if (styleableSkin)
						styleableSkin.styleName = this;

					if(embedTextField)
						addChildAt(DisplayObject(newSkin),getChildIndex(embedTextField));
					else
						addChild(DisplayObject(newSkin));

					// If the skin is programmatic, and we've already been
					// initialized, update it now to avoid flicker.
					if (newSkin is IInvalidating && initialized){
						IInvalidating(newSkin).validateNow();
					}
					else if (newSkin is ProgrammaticSkin && initialized){
						ProgrammaticSkin(newSkin).invalidateDisplayList();
					}
				}
			}
			// If the skin is already created then redraw it
			// depending on the characters entered in the text input
			else{
				ProgrammaticSkin(newSkin).invalidateDisplayList();
			}
		}
	} 
}
